<?php /*>*/ if (!defined('PmWiki')) exit();
/*  Copyright 2007-2010 by D.Faure (dominique.faure@gmail.com) and
    Patrick R. Michaud (pmichaud@pobox.com)
    Thanks to Steve Levithan for the unvaluable regex trick
    (http://blog.stevenlevithan.com/archives/mimic-atomic-groups/).
    This file would surely made be part of PmWiki; you can redistribute
    it and/or modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.

    This script extends the original MarkupExpressions recipe (core built-
    in since version 2.2.0-beta43) essentially by enabling *real* 
    expression nesting (ie. able to handle arithmetic operations). It also 
    defines various extra functions ranging from basic math to advanced 
    string manipulation.

    The operations defined by this recipe include:
      add, sub, mul, div, mod - arithmetic operators
      rev                     - reverse string
      rot13                   - rotate characters
      urlencode, urldecode    - url formatting
      reg_replace             - regexp find/replace 
      wikiword                - build a wikiword from a string
      test                    - evaluate a wiki condition
      if                      - conditionally returns its arguments
      sprintf                 - string formatting
      nomarkup                - keep text from a markup string

    The original "ftime" expression is also patched to enable some of the 
    format specifier missing on Win32 platforms.

    The recipe also handle some configuration flags applying deeper 
    modifications to the original script behavior:
      $EnableExprMultiline - handle of multiline expressions
      $EnableExprVarManip  - enable variable manipulation through
                             "set" and "setq" extra functions

    See http://www.pmwiki.org/wiki/Cookbook/MarkupExprPlus for more info.
*/
$RecipeInfo['MarkupExprPlus']['Version'] = '2010-09-01';

if(! IsEnabled($EnableMarkupExpressions, 1)) return;

SDVA($MarkupExpr, array(
  'add' => 'MEP_arith("+", preg_replace($rpat, $rrep, $params))',
  'sub' => 'MEP_arith("-", preg_replace($rpat, $rrep, $params))',
  'mul' => 'MEP_arith("*", preg_replace($rpat, $rrep, $params))',
  'div' => 'MEP_arith("/", preg_replace($rpat, $rrep, $params))',
  'mod' => '0 + ($args[0] % $args[1])',
  'rev' => 'strrev($args[0])',
  'urlencode' => 'rawurlencode($args[0])',
  'urldecode' => 'rawurldecode($args[0])',
  'rot13' => 'str_rot13($args[0])',
  'ftime' => 'MEP_ftime($args, $argp)',
  'wikiword' => 'preg_replace(array_keys($GLOBALS["MakePageNamePatterns"]),'
                           . 'array_values($GLOBALS["MakePageNamePatterns"]),'
                           . 'preg_replace($rpat, $rrep, $params))',
  'reg_replace' => 'MEP_reg_replace($args[0], $args[1], $args[2])',
  'test' => 'CondText($pagename, "if " . preg_replace($rpat, $rrep, $params), "TRUE") ? "1" : "0"',
  'if' => '($args[0]) ? $args[1] : (($args[2]) ? $args[2] : "")',
  'sprintf' => 'call_user_func_array("sprintf", $args)',
  'nomarkup' => 'preg_replace("/(<[^>]+>|\r\n?|\n\r?)/", "",'
                           . ' MarkupToHTML($pagename, $args[0], array("escape" => 0)))',
));

if(!function_exists('MarkupExpression'))
  if(file_exists("$FarmD/scripts/markupexpr.php"))
    include_once("$FarmD/scripts/markupexpr.php");
  else
    Abort("?MarkupExprPlus require MarkupExpression");

if(IsEnabled($EnableExprMultiline, 0))
  SDVA($MEP_Cfg, array(
  'MarkupRe' => '/\\{(\\(\\w+\\b.*?\\))\\}/se',
  'KeepRe' => '/([\'"])(.*?)\\1/se',
  'FuncRe' => '/\((?=((\\w+)(\\s[^()]*)?))\1\)/s',
  'ParseArgs' => 'MEP_ParseArgs',
  ));
SDVA($MEP_Cfg, array(
  'MarkupRe' => '/\\{(\\(\\w+\\b.*?\\))\\}/e',
  'KeepRe' => '/([\'"])(.*?)\\1/e',
  'FuncRe' => '/\((?=((\\w+)(\\s[^()]*)?))\1\)/',
  'ParseArgs' => 'ParseArgs',
  ));

if(IsEnabled($EnableExprVarManip, 0)) {
  DisableMarkup('{(');
  Markup('{(+', isset($MarkupTable['{$?|!@~var}']) ? '<{$?|!@~var}' : '<{$var}',
         $MEP_Cfg['MarkupRe'], "MarkupExpressionPlus(\$pagename, PSS('$1'))");
  SDVA($MarkupExpr, array(
    'set' =>  'MEP_setvar($pagename, true,  @$args[0], @$args[1], $argp)',
    'setq' => 'MEP_setvar($pagename, false, @$args[0], @$args[1], $argp)',
  ));
}
else {
  $MarkupTable['{(']['pat'] = $MEP_Cfg['MarkupRe'];
  $MarkupTable['{(']['rep'] = "MarkupExpressionPlus(\$pagename, PSS('$1'))";
}

function MarkupExpressionPlus($pagename, $expr) {
  global $EnableExprVarManip, $MarkupTable,
         $KeepToken, $KPV, $MarkupExpr, $MEP_Cfg;
  if(IsEnabled($EnableExprVarManip, 0)) {
    if(isset($MarkupTable['{$?|!@~var}']))
      while(preg_match($MarkupTable['{$?|!@~var}']['pat'], $expr))
        $expr = preg_replace($MarkupTable['{$?|!@~var}']['pat'],
                             $MarkupTable['{$?|!@~var}']['rep'], $expr);
    while(preg_match($MarkupTable['{$var}']['pat'], $expr))
      $expr = preg_replace($MarkupTable['{$var}']['pat'],
                           $MarkupTable['{$var}']['rep'], $expr);
  }
  $rpat = "/$KeepToken(\\d+P)$KeepToken/e";
  $rrep = '$KPV[\'$1\']';
  $expr = preg_replace($MEP_Cfg['KeepRe'], "Keep(PSS('$2'),'P')", $expr);
#  $expr = preg_replace('/\\(\\W/e', "Keep(PSS('$2'),'P')", $expr);
  while (preg_match($MEP_Cfg['FuncRe'], $expr, $match)) {
    @list($repl, $dummy, $func, $params) = $match;
    $code = @$MarkupExpr[$func];
    ##  if not a valid function, save this string as-is and exit
    if (!$code) break;
    ##  if the code uses '$params', we just evaluate directly
    if (strpos($code, '$params') !== false) {
      $out = eval("return ({$code});");
      if ($expr == $repl) { $expr = $out; break; }
      $expr = str_replace($repl, $out, $expr);
      continue;
    }
    ##  otherwise, we parse arguments into $args before evaluating
    $argp = $MEP_Cfg['ParseArgs']($params);
    $x = @$argp['#']; $args = array();
    while ($x) {
      list($k, $v) = array_splice($x, 0, 2);
      if ($k == '' || $k == '+' || $k == '-') 
        $args[] = $k.preg_replace($rpat, $rrep, $v);
    }
    ##  fix any quoted arguments
    foreach ($argp as $k => $v)
      if (!is_array($v)) $argp[$k] = preg_replace($rpat, $rrep, $v);
    $out = eval("return ({$code});");
    if ($expr == $repl) { $expr = $out; break; }
    $expr = str_replace($repl, Keep($out, 'P'), $expr);
  }
  return preg_replace($rpat, $rrep, $expr);
}


function MEP_ParseArgs($x, $optpat = '(?>(\\w+)[:=])') {
  $z = array();
  preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/s",
    $x, $terms, PREG_SET_ORDER);
  foreach($terms as $t) {
    $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]);
    if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; }
    else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; }
    $z['#'][] = $v;
  }
  return $z;
}


function MEP_nums($params) {
  $params = preg_split('/\\s+/', $params);
  $args = array();
  foreach($params as $p) {
    list($d) = sscanf($p, "%g");
    if(is_numeric($d)) $args[] = $d;
  }
  return $args;
}

##  MEP_arith handles {(add ...)}, {(sub ...)}, {(mul ...)} and {(div ...)}
##  expressions.
##
function MEP_arith($op, $params) {
  $expr = implode(")$op(", MEP_nums($params));
  return $expr ? eval("return 0 + ($expr);") : '';
}

function MEP_strftime($fmt, $ts = null, $lc = null, $gm = false) {
  if (!$ts) $ts = time();
  if(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    $date = $gm ? 'gmdate' : 'date';
    $map = array(
    '%C' => sprintf("%02d", $date("Y", $ts) / 100),
    '%D' => '%m/%d/%y',
    '%e' => sprintf("%' 2d", $date("j", $ts)),
    '%g' => sprintf("%02d", $date("o", $ts) % 100),
    '%G' => $date("o", $ts),
    '%h' => '%b',
    '%n' => "\n",
    '%r' => $date("h:i:s", $ts) . " %p",
    '%R' => $date("H:i", $ts),
    '%t' => "\t",
    '%T' => '%H:%M:%S',
    '%u' => ($w = $date("w", $ts)) ? $w : 7,
    '%V' => $date("W", $ts),
    '%z' => substr($date("O", $ts), 0, 3) . ":" . substr($date("O", $ts), 3),
    );
    $fmt = str_replace(array_keys($map), array_values($map), $fmt);
  }
  ##  make sure we have %F available for ISO dates
  $fmt = str_replace(array('%F', '%s'), array('%Y-%m-%d', $ts), $fmt);
  $strftime = $gm ? 'gmstrftime' : 'strftime';
  if(isset($lc)) {
     $oldlc = setlocale(LC_ALL, '0');
     setlocale(LC_ALL, $lc);
  }
  $ret = $strftime($fmt, $ts);
  if(isset($lc)) setlocale(LC_ALL, $oldlc);
  return $ret;
}

##  MEP_ftime handles {(ftime ...)} expressions.
##
function MEP_ftime($args, $argp = NULL) {
  global $TimeFmt, $Now, $FTimeFmt;
  ## get the format string
  if(@$argp['fmt']) $fmt = $argp['fmt'];
  else if(strpos(@$args[0], '%') !== false) $fmt = array_shift($args);
  else if(strpos(@$args[1], '%') !== false) list($fmt) = array_splice($args, 1, 1);
  else { SDV($FTimeFmt, $TimeFmt); $fmt = $FTimeFmt; }
  ## determine the timestamp
  if(isset($argp['when'])) list($time, $x) = DRange($argp['when']);
  else if(@$args[0] > '') list($time, $x) = DRange(array_shift($args));
  else $time = $Now;
  ## get the locale
  $locale = isset($argp['lc']) ? $argp['lc'] : array_shift($args);
  return MEP_strftime($fmt, $time, $locale);
}

##  MEP_setvar handles {(set ...)}/{(setq ...)} expressions.
##
function MEP_setvar($pagename, $parm, $arg0, $arg1, $argp = NULL) {
  global $FmtPV;
  $n = (@$argp['var']) ? $argp['var'] : $arg0;
  $v = (@$argp['value']) ? $argp['value'] : $arg1;
  if($n) $FmtPV["\${$n}"] = "'" . str_replace("'", "\\'", $v) . "'";
  return $parm ? $v : '';
}

##  MEP_reg_replace handles {(reg_replace /regexp/opt replacement string)}
##
function MEP_reg_replace($pat, $repl, $str) {
  if(!preg_match('/^(.)([^\\1]*)(\\1)(\\w*)$/', $pat, $m)) return $str;
  $m[4] = str_replace('e', '', $m[4]);
  return preg_replace(implode('', array_slice($m, 1)), $repl, $str);
}

